﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;

namespace Alex_s_Dream_The_Toy_World
{
    public delegate void EngineMessageHandler(IMessage message);

    public class FrameworkCore : Microsoft.Xna.Framework.Game
    {
        #region Fields

        public const int UniqueIDEmpty = -1;
        static TimeSpan elapsedDeltaTime = TimeSpan.Zero;
        private uint messagesSentThisFrame;
        private float partialSecondsThisFrame;
        private bool drawShadowMapTextureToScreen;
        private bool exiting = false;
        private GameTime gameTime;
        private Int64 terrainID = -1;
        public const int FirstEntityIDAbleToBeAltered = 2;    // No entity with an ID less than 2 is allowed to be deleted, unless the engine is shutting down

        static ScreenManager screenManager = null;
        /// <summary>
        /// This event is raised when there are messages in the queue
        /// </summary>
        /// <remarks>
        /// The message is only raised during a update loop
        /// </remarks>
        public event EngineMessageHandler GameMessage;
        private readonly GraphicsSystem graphics;
        private Settings settings;
        private static readonly object messageLock = new object();
        private PhysicsManager physics;
        private readonly ConfigurationManager configurationManager;
        private GameplayScreen sceneManager;
        private readonly StaticModelLoader staticModelLoader;
        private readonly AnimationModelLoader animationModelLoader;

        internal List<IMessage> handledMessages;
        internal List<IMessage> queuedMessages;
        internal List<IMessage> currentMessages;
        public event EventHandler<EventArgs> GameExiting;
        private readonly List<BaseManager> managers;


        #endregion

        #region Properties

        public ScreenManager ScreenManager
        {
            get { return screenManager; }
        }

        /// <summary>
        /// Retrieves the graphics system.
        /// </summary>
        public GraphicsSystem Graphics
        {
            get { return this.graphics; }
        }

        /// <summary>
        /// Gets the settings for the game.
        /// </summary>
        /// <remarks>
        /// Inheritors can use this property to set the settings instance.
        /// </remarks>
        public Settings Settings
        {
            get { return this.settings; }
            protected set { this.settings = value; }
        }

        public uint MessagesSentThisFrame
        {
            get { return messagesSentThisFrame; }
        }

        public float PartialSecondsThisFrame
        {
            get { return partialSecondsThisFrame; }
        }

        public bool DrawShadowMapTextureToScreen
        {
            get { return drawShadowMapTextureToScreen; }
            set { drawShadowMapTextureToScreen = value; }
        }

        /// <summary>
        /// Retrieves the physics system.
        /// </summary>
        public PhysicsManager Physics
        {
            get { return this.physics; }
        }

        /// <summary>
        /// Retrieves the global configuration manager.
        /// </summary>
        public ConfigurationManager ConfigurationManager
        {
            get { return this.configurationManager; }
        }

        public GameTime GameTime
        {
            get { return gameTime; }
        }

        /// <summary>
        /// Should only be 'set' by <see cref="Terrain"/>.
        /// </summary>
        public Int64 TerrainID
        {
            get { return terrainID; }
            set { terrainID = value; }
        }

        public bool FrameworkCoreExiting
        {
            get { return exiting; }
            set { exiting = value; }
        }

        public GameplayScreen SceneManager
        {
            get { return this.sceneManager; }
            set { this.sceneManager = value; }
        }

        /// <summary>
        /// Gets the <see cref="StaticModelLoader"/>
        /// </summary>
        public StaticModelLoader StaticModelLoader
        {
            get { return this.staticModelLoader; }
        }

        /// <summary>
        /// Gets the <see cref="AnimationModelLoader"/>
        /// </summary>
        public AnimationModelLoader AnimationModelLoader
        {
            get { return this.animationModelLoader; }
        }

        #endregion

        #region Initialize

        /// <summary>
        /// Core framework constructor.
        /// </summary>
        public FrameworkCore() : base()
        {
            this.configurationManager = new ConfigurationManager();
            this.graphics = new GraphicsSystem(this);
            this.staticModelLoader = new StaticModelLoader(this.Content);
            this.animationModelLoader = new AnimationModelLoader(this.Content);
            screenManager = new ScreenManager(this);

            this.managers = new List<BaseManager>();
            this.queuedMessages = new List<IMessage>();
            this.handledMessages = new List<IMessage>();
            this.currentMessages = new List<IMessage>();

            //  Entry GameScreenManager
            //AddComponent(screenManager);

            // Update as fast as possible, do not use fixed time steps
            IsFixedTimeStep = false;
        }

        /// <summary>
        /// Initialize all manangers and informations.
        /// </summary>
        protected override void Initialize()
        {
            this.Content.RootDirectory = "Content";

            this.InitializeSettings();
            this.InitializeConfiguration();
            this.InitializeManagers();
            this.InitializePhysics();

            ScreenManager.Initialize();
            base.Initialize();
        }

        /// <summary>
        /// Initializes the game settings
        /// </summary>
        protected virtual void InitializeSettings()
        {
            using (FileStream fs = new FileStream("Content/" + "Settings.xml", FileMode.Open, FileAccess.Read, FileShare.None))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Settings));
                settings = (Settings)serializer.Deserialize(fs);
            }
        }

        /// <summary>
        /// Initializes the <see cref="ConfigurationManager"/>
        /// </summary>
        private void InitializeConfiguration()
        {
            this.configurationManager.Initialize();
        }

        /// <summary>
        /// Initializes the input managers
        /// </summary>
        private void InitializeManagers()
        {
            this.managers.AddRange(this.ConfigurationManager.GetManagers(this));

            this.managers.Sort(delegate(BaseManager left, BaseManager right)
            {
                return right.UpdateOrder.CompareTo(left.UpdateOrder);
            });

            for (int i = this.managers.Count - 1; i >= 0; --i)
            {
                BaseManager manager = this.managers[i];
                manager.Initialize();
            }
        }

        /// <summary>
        /// Initializes the physics manager and configures the Physics property on QSGame.
        /// </summary>
        private void InitializePhysics()
        {
            for (int i = this.managers.Count - 1; i >= 0; --i)
            {
                BaseManager manager = this.managers[i];

                this.physics = manager as PhysicsManager;

                if (this.physics != null)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// Loads all core content that will be used through-out entire lifetime of game.
        /// </summary>
        protected override void LoadContent()
        {
            base.LoadContent();

            this.graphics.LoadContent();
        }

        /// <summary>
        /// Add a game component
        /// </summary>
        /// <param name="item">game component</param>
        public void AddComponent(GameComponent item)
        {
            Components.Add(item);
        }

        #endregion

        #region Update And Draw
        /// <summary>
        /// Update all managers and informations.
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            GraphicsDevice.Textures[0] = null;
            this.messagesSentThisFrame = 0;

            this.gameTime = gameTime;
            this.partialSecondsThisFrame = (float)(Math.Max(gameTime.ElapsedGameTime.Milliseconds, 1u) * 0.001f);

            if (this.exiting == true)
            {
                // Clear all other messages
                this.currentMessages.Clear();
                this.queuedMessages.Clear();
                this.handledMessages.Clear();

                // Empty out the pool
                ObjectPool.ReleaseAll();

                // Raise the exit event
                this.OnGameExiting();

                this.Exit();
                return;
            }

            this.HandleMessages();

            for (int i = this.managers.Count - 1; i >= 0; --i)
            {
                BaseManager manager = this.managers[i];
                manager.Update(gameTime);
            }

            ScreenManager.Update(gameTime);
            base.Update(gameTime);
        }

        /// <summary>
        /// Raises the <see cref="GameExiting"/> event
        /// </summary>
        protected virtual void OnGameExiting()
        {
            if (this.GameExiting != null)
            {
                this.GameExiting(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Clears the list of messages and release the aquired messages
        /// </summary>
        /// <param name="list">List of <see cref="IMessage"/> to be cleared</param>
        private static void ClearMessages(IList<IMessage> list)
        {
            while (list.Count > 0)
            {
                IMessage message = list[0];
                list.RemoveAt(0);

                ObjectPool.Release(message);
            }
        }

        /// <summary>
        /// This handles messages and raises the <see cref="QSGame.GameMessage"/> if needed
        /// </summary>
        private void HandleMessages()
        {
            if (this.GameMessage != null)
            {
                lock (messageLock)
                {
                    List<IMessage> temp = this.currentMessages;
                    this.currentMessages = this.queuedMessages;
                    this.queuedMessages = temp;
                    this.queuedMessages.Clear();
                }

                for (int i = 0; i < this.currentMessages.Count; ++i)
                {
                    if (this.exiting == true)
                    {
                        break;
                    }

                    // Send message to entire system
                    this.OnGameMessage(this.currentMessages[i]);
                }
            }

            ClearMessages(this.currentMessages);

            if (this.GameMessage != null)
            {
                lock (messageLock)
                {
                    List<IMessage> tempHandled = this.currentMessages;
                    this.currentMessages = this.handledMessages;
                    this.handledMessages = tempHandled;
                    this.handledMessages.Clear();
                }
            }

            ClearMessages(this.currentMessages);
        }

        /// <summary>
        /// Begins a rendering frame.
        /// </summary>
        /// <returns>True if rendering can proceed, false otherwise.</returns>
        protected override bool BeginDraw()
        {
            return base.BeginDraw();
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        protected override void Draw(GameTime gameTime)
        {
            ScreenManager.Draw(gameTime);
            base.Draw(gameTime);
        }

        /// <summary>
        /// Ends a rendering frame.
        /// </summary>
        protected override void EndDraw()
        {
            base.EndDraw();
        }

        #endregion

        #region Dispose

        /// <summary>
        /// Dispose all managers.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (screenManager != null)
            {
                screenManager = null;
            }

            base.Dispose(disposing);
        }

        #endregion

        #region Message

        /// <summary>
        /// Sends messages to the system
        /// </summary>
        /// <param name="message">The <see cref="IMessage"/> which should be send</param>
        /// <remarks>Use <see cref="Message<T>"/> when sending errors</remarks>
        public void SendMessage(IMessage message)
        {
            ++this.messagesSentThisFrame;

            if (!PreProcessMessage(message))
                return;

            this.OnGameMessage(message);

            ProcessMessageHandled(message);
        }

        /// <summary>
        /// Sends messages to all registered QSInterface instances only.
        /// </summary>
        /// <param name="message">The <see cref="IMessage"/> which should be sent</param>
        /// <param name="interfaceYype">The <see cref="InterfaceType"/> to send the message to</param>
        /// <remarks>Use <see cref="Message<T>"/> when sending errors</remarks>
        public void SendInterfaceMessage(IMessage message, InterfaceType interfaceType)
        {
            ++this.messagesSentThisFrame;

            if (this.SceneManager != null)
            {
                if (!this.SceneManager.ForwardInterfaceMessage(message, interfaceType))
                {
                    throw new Exception("The interface does not yet exist, hasn't been initialized with the scene manager, or didn't handle the message type it was sent.");
                }

                ProcessMessageHandled(message);
            }
        }

        private bool PreProcessMessage(IMessage message)
        {
            switch (message.Type)
            {
                case MessageType.Unknown:
                    {
                        throw new Exception("Message must be given a type");
                    }
                case MessageType.Shutdown:
                    {
                        // @TODO: Rather than simply closing the program, we should notify all systems and let
                        //          them shutdown first in case they'd like to do something like save data.
                        this.exiting = true;
                        return false;
                    }
                default:
                    break;
            }

            return true;
        }

        public void ProcessMessageHandled(IMessage message)
        {
            if (!message.GetHandled())
            {
                lock (FrameworkCore.messageLock)
                {
                    message.SetHandled(true);
                    this.handledMessages.Add(message);
                }
            }
            else
            {
                throw new Exception("Message was improperly sent. Did you send the same message twice without re-aquiring memory for it from the ObjectPool?");
            }
        }

        /// <summary>
        /// Raises the <see cref="GameMessage"/> event
        /// </summary>
        /// <param name="message">The <see cref="IMessage"/> to raise</param>
        protected virtual void OnGameMessage(IMessage message)
        {
            if (this.GameMessage != null)
            {
                if (message.UniqueTarget == UniqueIDEmpty)
                {
                    this.GameMessage(message);
                }
                else
                {
                    BaseEntity target;
                    if (this.SceneManager != null)
                    {
                        if (this.SceneManager.Entities.TryGetValue(message.UniqueTarget, out target))
                        {
                            target.ExecuteMessage(message);
                        }
                    }
                }
            }
        }

        public void QueueMessage(IMessage message)
        {
            if (!PreProcessMessage(message))
                return;

            // Lock the message queue before adding message
            lock (FrameworkCore.messageLock)
            {
                this.queuedMessages.Add(message);
            }
        }

        #endregion

        protected override void UnloadContent()
        {
            base.UnloadContent();
        }
    }
}
